Logging From Launchd

At some point, perhaps OS X 10.6 (Snow Leopard), the launchd utility was changed so that normal messages to stdout and stderr are no longer logged in such a way that they can be viewed in Console.app. This is true even if you set the Debug key in the my_item.plist file. In fact, the only thing that is logged to console any more is if your item exits with a non-zero status. So if your item exits with an error status, you do not see any error messages that may have been written prior to exit. Worse, if there's a mistake in your script’s error handling such that some subtask has an error, but the overall task exits with status 0, you never see any message at all.

Searches for Mac-specific solutions turn up a number of suggestions to use the StandardOutputPath and StandardErrorPath keys to capture messages into log files. This is a fine suggestion for capturing the output somewhere so that there is at least some ability to get at the messages, confirm proper operation, investigate errors, and so on. But it still does not get messages logged into the central syslog facility where they can be viewed together with everything else in Console.app.

There is a way to redirect stdout & stderr into syslog, using the exec shell builtin combined with the logger command. This blog entry has the following example, which puts messages into syslog, tagged with the name of the executing script, and also echoes them out to normal stdout/stderr for when you’re running the script in an interactive terminal session (development & debugging for instance). The post has a nice explanation of how it works, and examples of a couple of different ways of trading off preserving order of messages between stdout & stderr vs having them in separate streams.

exec 1> >(logger -s -t $(basename $0)) 2>&1

There’s also this tiny sample on git showing another approach, tagging stdout & stderr with different levels, and not echoing them into an interactive session.

exec > >(logger -p user.info) 2> >(logger -p user.warn)

Note that the default on OS X is to only display messages at level warn or more severe. So on a Mac, this example actually loses stdout, and you’d probably want to use different levels.

exec > >(logger -p user.warn) 2> >(logger -p user.error)

Here’s what I chose to do, which preserves order, loses the distinction between stdout vs stderr, and echoes to an interactive terminal session the original messages without the extra information that logger adds for syslog use:

exec 1> >(tee >(logger -t $(basename $0))) 2>&1

And, finally, a little test script you can use to verify how it logs, both to a terminal session and to syslog for viewing in Console.app, “developed” and verified in OS X 10.10.2 (Yosemite):

#!/bin/bash
exec 1> >(tee >(logger -t $(basename $0))) 2>&1
echo "*** my script *** stdout"
>&2 echo "*** my script *** stderr"